#pragma once

#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <functional>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>// sockaddr_in

namespace server
{
    const static std::string defaultIp = "0.0.0.0";// 服务器默认ip   TODO
    // or 在类内是用const static int
    const static int gnum = 1024;

    enum
    {
        USAGE_ERROR = 1,
        SOCKET_ERROR,
        BIND_ERROR,
        OPEN_ERROR,
        TXT_ERROR
    };

    typedef std::function<void(int, std::string&, uint16_t, std::string&)> func_t;

    class udpServer
    {
    public:
        //
        udpServer(const func_t& callback, const uint16_t &port, const std::string& ip = defaultIp)
            : _callback(callback), _port(port), _ip(ip), _sockfd(-1)
        {}
        ~udpServer()
        {
        }

        //
        void initServer()
        {
            // 1. 创建套接字-得到一个文件——系统层次
            _sockfd = socket(AF_INET, SOCK_DGRAM, 0);// 这里的AF_INET表示创建用于网络通信的套接字
            if(_sockfd == -1)
            {
                std::cerr << "socket error: " << errno << " : " << strerror(errno) << std::endl;
                exit(SOCKET_ERROR);// 创建套接字失败，就没必要继续了
            }
            std::cerr << "socket success and sockfd: " << _sockfd << std::endl;// 测试用


            /* 套接字其实就是一个用于通信的文件，还需要为其绑定ip和端口号 */

            // 2. 为套接字绑定ip和端口号——网络层次
            struct sockaddr_in local;// 位于用户栈的变量

            // struct sockaddr_in结构中存在填充字段，虽然没什么意义，但最好还是保证结构中的各字段为空
            bzero(&local, sizeof(local));
            
            // 填充字段
            local.sin_family = AF_INET;// 协议家族。这里的AF_INET表示struct sockaddr_in local用于网络通信
            /* 向对方发送消息时，自己的port和ip也要携带着发送给对方 */ 
            local.sin_port = htons(_port);/* 因为port是2字节（多字节）的数据，这个数据要发送给其他端，所以要考虑大小端问题！下面的ip同理 */
            local.sin_addr.s_addr = inet_addr(_ip.c_str());/* 这里不能简单的使用atoi()，因为这个整数是有一定的格式的，使用系统提供的接口 */

            // 绑定
            int n = bind(_sockfd, (struct sockaddr*)&local, sizeof(local));
            if(n == -1)
            {
                std::cerr << "bind error: " << errno << " : " << strerror(errno) << std::endl;
                exit(BIND_ERROR);
            }

            // udpServer的预备工作完成
        }
        void start()
        {
            // 服务器应是一直运行的，死循环。这种被称为常驻内存。
            while(true)
            {
                sleep(1);

                // 1. 接收数据
                struct sockaddr_in origin;
                socklen_t len = sizeof(origin);

                char buffer[gnum] = { 0 };// 缓冲区buffer需要清空，防止出现输出错误
                // memset(buffer, 0, gnum);
                ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&origin, &len);// -1 -> 当成字符串处理
                buffer[n] = 0;
                // 2. 读取数据
                if(n > 0)
                {
                    // a. IP
                    std::string clientIp = inet_ntoa(origin.sin_addr);// 1. 网络序列转主机序列 2. 数字->字符串（点分十进制IP）
                    // b. port
                    int clientPort = ntohs(origin.sin_port);// 网络序列转主机序列
                    // c. message
                    std::string msg = buffer;

                    std::cout << clientIp << "[" << clientPort << "]# " << msg << std::endl;
                    
                    // 3. 处理数据
                    _callback(_sockfd, clientIp, clientPort, msg);
                }
            }
        }
    private:
        uint16_t _port;// 服务器绑定的端口号
        std::string _ip;// 服务器的ip地址
        int _sockfd;// 创建的套接字
        func_t _callback;// 回调函数，处理数据
    };
}